#include <stdio.h>
#include <stdlib.h>
#include <gccore.h>
#include <ogcsys.h>
#include <sys/types.h>
#include <sys/dir.h>
#include <stddef.h>
#include <time.h>

#include <unistd.h>

#include <string>
#include <vector>

#include <wiiuse/wpad.h>

#include <fat.h>
#include <wiisprite.h>

#include <mxml.h>

#include "OperationLevel.h"
#include "OperationAudio.h"
#include "OperationText.h"

#include "font/font1.h"
#include "TextHelper.h"

#define RUMBLE_TIME 20
#define NUM_LIVES 3

static void *xfb = NULL;
static GXRModeObj *rmode = NULL;

Mtx GXmodelView2D;

using namespace wsp;
bool can_tweeze = true;

OperationLevel* operationLevel;
OperationAudio* operationAudio;
OperationText*  operationText;

bool wait_for_next = false;
unsigned int current_level = 0;
bool failed = false;
bool showHelp = false;
//Images
Image tweezer_img;
Image pinch_img;
Image hand_img;
Image menu_img;
Image wait_img;
Image help_img;

Image life_img;
Image death_img;

Sprite tweezer;
Sprite menu;
Sprite wait;
Sprite help;

Sprite lifeSprite[NUM_LIVES];
bool lifeStatus[NUM_LIVES];

Rectangle NewGameOption;
Rectangle HelpOption;
Rectangle QuitOption;

GameWindow gwd;

bool isGrabbing;
bool inMenu = true;

f32 zoom_value = 1;

std::vector<std::string> level_files;

u8 * tex_font1;

char* high_score_file = "/data/operation/high_scores.xml";

//Timing Logic
time_t start_time;
time_t end_time;
double best_time = 0.0;


Font *font;
Text text;
	
typedef struct {
   char*  level;
   double best_time;
} Score;

std::vector<Score> high_scores;

void StartTimer()
{
   time(&start_time);
   time(&end_time);
}

void UpdateTimer()
{
   time(&end_time);
}

void init_console()
{
	// Initialise the video system
	VIDEO_Init();
	// Obtain the preferred video mode from the system
	// This will correspond to the settings in the Wii menu
	rmode = VIDEO_GetPreferredMode(NULL);

	// Allocate memory for the display in the uncached region
	xfb = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));
	
	// Initialise the console, required for printf
	console_init(xfb,20,20,rmode->fbWidth,rmode->xfbHeight,rmode->fbWidth*VI_DISPLAY_PIX_SZ);
	
	// Set up the video registers with the chosen mode
	VIDEO_Configure(rmode);
	
	// Tell the video hardware where our display memory is
	VIDEO_SetNextFramebuffer(xfb);
	
	// Make the display visible
	VIDEO_SetBlack(FALSE);

	// Flush the video register changes to the hardware
	VIDEO_Flush();

	// Wait for Video setup to complete
	VIDEO_WaitVSync();
	if(rmode->viTVMode&VI_NON_INTERLACE) VIDEO_WaitVSync();

}

void init_wii()
{
	WPAD_Init();
	gwd.InitVideo();
	fatInitDefault();
	WPAD_SetDataFormat(WPAD_CHAN_0, WPAD_FMT_BTNS_ACC_IR);
}

void LoadLevels()
{
   char filename[MAXPATHLEN];
   char tname[MAXPATHLEN+7];
   DIR_ITER *dp;
   struct stat st;
   
   dp = diropen("/data/operation/levels/");
   if(dp != NULL)
   {
      while(dirnext(dp,filename,&st) == 0)
      {
         if ((st.st_mode & S_IFDIR ? "DIR":"FILE")=="FILE")
         {
            sprintf(tname, "/data/operation/levels/%s",filename);
            std::string str = tname;
            level_files.push_back(str);
         }
      }
      (void) dirclose(dp);
   }
  
   
  
}

void ReinitLives()
{
   int LIFE_Y_LOC    = 420;
   int LIFE_X_START  = 40;
   int LIFE_X_OFFSET = 70;
   
   for(int i = 0; i < NUM_LIVES; i++)
   {
      lifeSprite[i].SetImage(&life_img);
      lifeSprite[i].SetPosition(LIFE_X_START+(i*LIFE_X_OFFSET), LIFE_Y_LOC);
	  lifeStatus[i] = true;
   }
}

void SaveHighScores(char* score_file)
{
   mxml_node_t *xml;
   mxml_node_t *tree;
   mxml_node_t *data;
   mxml_node_t *group;
   
   xml = mxmlNewXML("1.0");
   char time_str[50];
   
   tree = mxmlNewElement(xml, "data");
   
   for(unsigned int i = 0; i < high_scores.size(); i++)
   {
      data = mxmlNewElement(tree, "score");
      mxmlElementSetAttr(data, "level",high_scores.at(i).level);
	  sprintf(time_str, "%f", high_scores.at(i).best_time);
	  mxmlElementSetAttr(data, "best_time", time_str);
   }
   FILE *fp;
   fp = fopen(score_file, "w");
   mxmlSaveFile(xml, fp, MXML_NO_CALLBACK);
   fclose(fp);
}

void LoadHighScores(char* score_file)
{
  FILE *fp;
  mxml_node_t *tree;
  mxml_node_t *data;
  mxml_node_t *score;

  printf("Load Scores (%s)\n", score_file);
  high_scores.clear();

  printf("Open File...\n");
  fp = fopen(score_file, "r");
  if(fp == NULL) return;

  tree = mxmlLoadFile(NULL, fp, MXML_TEXT_CALLBACK);
  fclose(fp);

  data = mxmlFindElement(tree, tree, "data", NULL, NULL, MXML_DESCEND); 
  if(data == NULL) return;
  
    for(score = mxmlFindElement(data, data, "score", NULL, NULL, MXML_DESCEND); 
        score != NULL; 
        score = mxmlFindElement(score, data, "score", NULL, NULL, MXML_DESCEND))
  {
     Score s;
	 s.level = (char*)(mxmlElementGetAttr(score,"level"));
	 s.best_time = atof(mxmlElementGetAttr(score,"best_time"));
	 high_scores.push_back(s);
  }
  
  return;
}

void init_game()
{
/*
   printf("Load Textrue...\n");
   font = new Font("/operation/images/font.png", 7, 13);
   font->load();

   text.setFont(font);
  */ 
   printf("Load Levels...\n");
   LoadLevels();
   
   printf("Construct Operation Level...\n");
   operationLevel = new OperationLevel();
   
   printf("Construct Operation Audio...\n");
   operationAudio = new OperationAudio();
   operationAudio->InitAudio();
   
   printf("Construct Wait Image...\n");
   if(wait_img.LoadImage("/data/operation/images/wait.png") != IMG_LOAD_ERROR_NONE)exit(0);
   wait.SetImage(&wait_img);
   wait.SetPosition(100,190);

   printf("Construct Help Image...\n");
   if(help_img.LoadImage("/data/operation/images/help.png") != IMG_LOAD_ERROR_NONE)exit(0);
   help.SetImage(&help_img);
   help.SetPosition(0,0);
   
   printf("Construct Pinch Image...\n");
   if(pinch_img.LoadImage("/data/operation/images/pinch.png") != IMG_LOAD_ERROR_NONE)exit(0);

   printf("Construct Life Image...\n");
   if(life_img.LoadImage("/data/operation/images/life.png") != IMG_LOAD_ERROR_NONE)exit(0);
   
   printf("Construct Death Image...\n");
   if(death_img.LoadImage("/data/operation/images/death.png") != IMG_LOAD_ERROR_NONE)exit(0);
   
   printf("Construct Tweezer Im...\n");
   if(tweezer_img.LoadImage("/data/operation/images/tweezer.png") != IMG_LOAD_ERROR_NONE)exit(0);

   printf("Construct main Image...\n");
   if(menu_img.LoadImage("/data/operation/images/main.png") != IMG_LOAD_ERROR_NONE)exit(0);
   menu.SetImage(&menu_img);
   menu.SetPosition(0,0);

   printf("Construct Hand Image...\n");
   if(hand_img.LoadImage("/data/operation/images/hand.png") != IMG_LOAD_ERROR_NONE)exit(0);
   tweezer.SetImage(&hand_img);
   tweezer.SetPosition(0,0);

  // printf("Load High Scores...\n");
 //  LoadHighScores(high_score_file);

   NewGameOption.x = 380;
   NewGameOption.y = 50;
   NewGameOption.height = 50;
   NewGameOption.width = 200;

   QuitOption.x = 380;
   QuitOption.y = 280;
   QuitOption.height = 50;
   QuitOption.width = 200;
   
   HelpOption.x = 380;
   HelpOption.y = 150;
   HelpOption.height = 50;
   HelpOption.width = 200;
   
   ReinitLives();
}

//return true if lives are left
bool RemoveLife()
{
   int i = NUM_LIVES-1;
   while(i > 0)
   {
      if(lifeStatus[i])
	  {
	     lifeStatus[i] = false;
		 lifeSprite[i].SetImage(&death_img);
		 return true;
      }
	  i--;
   }
   
   return false;

}

void StartGame()
{		
   operationAudio->StopGameMusic();
   operationAudio->PlayGameMusic();
	
   inMenu = false;
   current_level = 0;
   if(!(operationLevel->LoadLevel(level_files.at(current_level).c_str())))
   {
      printf("Failed to Load!!!\n");
      inMenu = true;
      failed = true;
   }

}

void NextLevel()
{
   bool found = false;
   
   ReinitLives();
   //WPAD_Rumble(WPAD_CHAN_0, 0);
   tweezer.SetImage(&hand_img);

   current_level++;
   if(current_level >= level_files.size())
      current_level = 0; //Start Over

   operationLevel->LoadLevel(level_files.at(current_level).c_str());   
   //find best time
   best_time = 0;
   /*for(unsigned int i = 0; i < high_scores.size(); i++)
   {
      if(strcmp(high_scores.at(i).level,operationLevel->GetTitle().c_str()) == 0)
	  {
	     best_time = high_scores.at(i).best_time;
		 found = true;
		 break;
	  }
	}
	
	//Not found so add this high score to the list so it will be found later.
	if(!found)
	{
	   Score new_score;
	   new_score.level = (char*)operationLevel->GetTitle().c_str();
	   new_score.best_time = 0;
	   high_scores.push_back(new_score);
	}
	*/
}

void draw_screen()
{
 if(!failed)
 {
  if(inMenu)
  {
     menu.Draw();
     tweezer.SetZoom(.75);
     tweezer.Draw();
     if(showHelp)
     {
         help.Draw();
     }
  }
  else
  {
     operationLevel->Draw(zoom_value);
     tweezer.SetZoom(zoom_value);
     tweezer.Draw();
	 
	 for(int i = 0; i < NUM_LIVES; i++)
        lifeSprite[i].Draw();	 
	 
	 
     if(wait_for_next)
     {
        wait.Draw();
		
     }
	/*
	char buf[100];
	
	sprintf(buf, "Time: %.2f", difftime(end_time, start_time));
	text.setText(buf);
	text.setPosition(500,400);
	text.draw();
	
	sprintf(buf, "Best Time: %.2f", best_time);
	text.setText(buf);
	text.setPosition(500,430);
	text.draw();
	*/
	
     if(showHelp)
     {
         help.Draw();
     }
  }


  gwd.Flush();
 }
 else
 {
	// Wait for Video setup to complete
	VIDEO_WaitVSync();

 }
}

//---------------------------------------------------------------------------------
int main(int argc, char **argv) {
//---------------------------------------------------------------------------------
	bool doRumble = false;
	int rumble_count = 0;
	bool firstPass = true;
	printf("Initialize Wii..\n");
	init_wii();
	
	init_console();

	printf("Initialize Game..\n");
	init_game();

	printf("Enter main game loop...\n");
	
	while(1) {

		if(operationLevel->IsLevelRunning() && can_tweeze)
		{
		   if(firstPass)
		   {
		      StartTimer();
			  firstPass = false;
		   }
		   UpdateTimer();
		   tweezer.SetImage(&tweezer_img);
		}
		else
		{
		   tweezer.SetImage(&hand_img);
		}

		isGrabbing = false;

		// Call WPAD_ScanPads each loop, this reads the latest controller states
		WPAD_ScanPads();

		ir_t ir;
		WPAD_IR(WPAD_CHAN_0, &ir);

		// WPAD_ButtonsDown tells us which buttons were pressed in this loop
		// this is a "one shot" state which will not fire again until the button has been released
		u32 pressed = WPAD_ButtonsDown(0);

		u32 held = WPAD_ButtonsHeld(0);

		// We return to the launcher application via exit
		if ( pressed & WPAD_BUTTON_HOME ) 
		{
		   showHelp = false;
		   WPAD_Rumble(WPAD_CHAN_0, 0);
		   operationLevel->EndLevel();
		   inMenu = true;
		   operationAudio->StopGameMusic();
		}

		// Show help
		if ( pressed & WPAD_BUTTON_1 ) 
		{
		   showHelp = !showHelp;
		}

		// We return to the launcher application via exit
		if ( pressed & WPAD_BUTTON_2 ) 
		{
		   showHelp = false;
		   if(wait_for_next)
		   {
			  wait_for_next = false;
			  NextLevel();
		   }
		}

		if ( (held & WPAD_BUTTON_A) && (held & WPAD_BUTTON_B) ) //Grab with tweezers
		{
		   showHelp = false;
		   if(!inMenu  && !wait_for_next)
		   {
			  if(operationLevel->IsLevelRunning())
			  {
				 tweezer.SetImage(&pinch_img); 
			  }
			  isGrabbing = true;
		   }
		}
		else
		{
		   if(!wait_for_next) {
			  can_tweeze = true;
		   }
		}

		f32 dx = (ir.sx-WSP_POINTER_CORRECTION_X) - tweezer.GetX();
			f32 dy = (ir.sy-WSP_POINTER_CORRECTION_Y) - tweezer.GetY();
			f32 d_ang = ir.angle/2 - tweezer.GetRotation();

			tweezer.SetPosition(ir.sx-WSP_POINTER_CORRECTION_X, ir.sy-WSP_POINTER_CORRECTION_Y);
			tweezer.SetRotation(ir.angle/2);

		if ( pressed & WPAD_BUTTON_A )
		{
		   showHelp = false;
		   if(!inMenu && !wait_for_next)
		   {
			  isGrabbing = true;
		   }
		   else if(tweezer.CollidesWith(&NewGameOption,true) && inMenu)
		   {
			  StartGame();
		   }
		   else if(tweezer.CollidesWith(&QuitOption,true) && inMenu)
		   {
		      //SaveHighScores(high_score_file);
			  return 0;
		   }
		   else if(tweezer.CollidesWith(&HelpOption,true) && inMenu)
		   {
			  showHelp = true;
		   }
		}

		if(!inMenu && !wait_for_next)
        {
		   if(!(operationLevel->Update(&tweezer, isGrabbing))) //returns false if level still running
		   {
			  if(operationLevel->IsBonePinched())
			  {
				 operationLevel->MoveBone(dx,dy);
				 operationLevel->RotateBone(d_ang);
			  }
		   }
		   else{
			  if(operationLevel->IsWon())
			  {
			     UpdateTimer();
				 wait_for_next = true;
				 can_tweeze = false;
			  }
			  if(operationLevel->IsCollision())
			  {
				 //BUZZ!!!           
				  rumble_count = 0;
				  doRumble = true;
				  WPAD_Rumble(WPAD_CHAN_0, 1);
				  bool lives_left = RemoveLife();
				  operationLevel->InitLevel(!lives_left);
				  if(!lives_left)
				  {
				     firstPass = true;
					 ReinitLives();
				  }
			  }
		   }
		}

		if(rumble_count >= RUMBLE_TIME)
		{
			WPAD_Rumble(WPAD_CHAN_0, 0);
			doRumble = false;
		}
		else if(doRumble)
		{
			rumble_count++;
		}
		draw_screen();
	}

	return 0;
}
